May 99 Getting Started
Volume Number: 15
Issue Number: 5
Column Tag: Getting Started
Resource Files
by Dan Parks Sydow
How a Mac program accesses resources stored in
external files
Understanding what resources are, and how to work with them, is critical to
programming the Mac. After even only the briefest introduction to Macintosh
programming, it becomes obvious to a programmer that it's resources that define the
interface of a Mac program. But resources can be used for much more than defining the
look of a program's menus, windows, and dialog boxes. A resource can be used to store a
sound, a few (or a few thousand) characters of stylized text, a program preference
(such as the screen placement of a window), or just about any other type of
information a clever developer can think of.
The resources that define a program's interface are invariably stored within the
application itself - within the program's resource fork. But a resource doesn't have to
be a part of a program in order to be used by that program. Instead, a resource can
reside in a separate resource file that the program opens and accesses. In this article
we take a look at why resources might be stored in a file external to a program, and
how a program accesses the resources in such a file. In next month's Getting Started
we'll expand on this month's techniques in order to develop a program that creates,
maintains, and uses, its own preferences file.
Resource Forks
The contents of a Macintosh file are said to exist in forks. There are two types of file
forks - the resource fork and the data fork. A Macintosh file can consist of either or
both types of forks (some will say that a file always consists of both forks, though one
fork may just happen to be empty). As you'd expect, the resource fork holds resources.
The data fork holds other types of information. Exactly what type of information is in a
file's data fork is dependent on the program that created the file. If you save a
SimpleText document, for instance, the resulting file will have a data fork that holds
the text of the saved document. You can see how much information is stored in both
forks of any one file by opening that file in ResEdit and choosing Get Info from the File
menu. Figure 1 shows that a small SimpleText text file named READ ME has 356
bytes in its resource fork and 4092 bytes in its data fork. Without a program like
ResEdit, you wouldn't know the exact byte values of the file's forks. But the general
result should be as you'd expect - a text file stores the file's characters in the data
fork, and has little need for resources (in this case SimpleText stores a resource that
keeps track of any stylized text that appears in the READ ME file).
Figure 1. Using ResEdit to find the size of a file's forks.
When viewed in a hex editor (a type of text editor that displays a file's contents in
hexadecimal format), the information in either fork appears to be simply a stream of
hexadecimal characters. Consider ResFiles, this month's example program. When a
PowerPC version of ResFiles is built, the Metrowerks linker merges the compiled
program code with the project resources. The result is a program that holds the
compiled code in its data fork and resources in its resource fork. In Figure 2 a
special hex editor named BrainHex (shareware available for downloading at
<http://www.psyber.com/~brainscn/brainhex.html>) is displaying the hex content of
both forks.
Figure 2. Using a hex editor to view the contents of both forks of a file.
In the sense that both forks hold a series of hex characters, the contents of the two
forks look similar. But there are differences. Those differences involve formatting -
the way in which special formatting characters appear throughout information in the
fork. When a program opens a file of its own creation, the program knows how to read
in the information from the file's data fork - and it knows how to format that
information. When a word processor opens one of its files, it knows which words
should be displayed in boldface, the font to use for each section of text, and so forth.
When a graphics program opens one of its files, it knows how to interpret the data fork
information and display the proper shapes in the appropriate colors, patterns, and so
on. Thus no single program can open and make use of the data fork of all types of files -
no one program knows how to format the data stored in every type of file. For a file's
resource fork, this isn't the case - there is a single program that can make sense of
the contents.
The information that makes up a resource follows a clearly defined format. A developer
who knows that format can write an editor that can open and display (in a meaningful
way) that resource. Regardless of the type of file the resource resides in (the resource
fork of an application, a word processor file, a graphics file, and so forth), the
resource editor can make sense of that resource. Apple has created such a program -
ResEdit. Apple isn't the only company that's created a resource editor, though. Most
notable of the third-party resource editors is the very useful Resorcerer by
Mathemaesthetics, Inc. (<http://www.mathemaesthetics.com>).
Applications and Resource Files
When a program reads information from a file - whether the information is a
resource or some other type of data - the operation is a relatively safe one. When an
error does occur, it more typically occurs when a program writes information to a
file. If a write operation fails, the result can be a corrupt file. A program is safe in
storing its interface-related resources in its own resource fork because the program
typically reads the resources from this fork, but seldom or never writes to this fork.
When a program opens a window, it may do so be reading WIND resource information
into memory. When a program displays a menu bar and menus, it may do so be reading
MBAR and MENU resources. In both cases no resource fork information is altered. If a
program is to work with resources that it may in fact alter, it is safer to store these
resources in a different resource fork. That is, these resources should be kept in the
resource fork of a separate file. Corruption of a file is always bad news, but if the
writing of a resource corrupts an external file, the damage is less costly than if the
writing of the resource corrupts the application itself.
A preferences file is a good example of the use of storing resources external to an
application. A preferences file doesn't have to be a resource file (software developers
can devise any number of schemes for storing user-configurable options in a file), but
it often is. The information in a preferences file can be altered by a program any time
the user specifies that some application setting should be changed (often be choices
made in a Preferences dialog box accessed through a Preferences item in the program's
Edit menu). If the changing of some information in a preferences file corrupts that
file, the application is not damaged. If a subsequent attempt to access the preferences
file fails, the application typically creates a new preferences file that holds some
default settings. The user will need to re-enter his or her preferences, but that's a
small price to pay to preserve the integrity of the application.
Avoiding application corruption is one reason to keep a resource in an external file.
Other reasons for storing resources outside of an application involve portability and
application updating. For example, a program that plays sounds may store those sounds
as sound resources in external resource files (refer to Getting Started in this year's
February issue of MacTech for the details of sound resource playing). To allow the
program to play more, or new, sounds, the user might then need to obtain only the
sound files. The user won't have to get a new copy of the entire application and then
reinstall the program (which would be the case if the sounds were all stored within
the application's resource fork). Carrying on with our sound playing program
example, imagine that the application allowed users to supply their own sounds. It's an
easy task to get sounds that are stored as resources: one can create, download, buy, or
swap files with others. So here again the user benefits - he or she can obtain sounds
and make use of them without having to obtain a new version of the sound playing
program.
Toggling Resource Forks
Before jumping into the specifics of working with resource files, a little
resource-related terminology is in order. When most or all of a file's information is
held in the file's resource fork, that file is often referred to as a resource file. When
that file's resource fork is being manipulated (opened, written to, read from, or
closed) by a program, programmers often refer to the fork itself as a file. When you
hear about a Toolbox routine being used to "open the resource file," that routine is in
fact opening the resource fork of the file. So in your readings of resource-related
material, you'll occasionally encounter the terms resource fork and resource file used
interchangeably. In this introductory article we'll make a special effort to stick with
fork when discussing the fork and file when discussing the file that owns the fork - but
be aware that a looseness of this terminology does exist. Here we'll also talk about an
external resource file. Of course any file is external to a program, but for clarity
we'll put an emphasis on external. That should help avoid confusion with a project
resource file, the contents of which become internal to an application and thus a part
of an application's internal resource fork.
When a program executes, its own resource fork is open - by default it's available for
reading from and writing to. This same program can open the resource forks from any
number of other files as well. When a program opens a resource fork (including its
own), the File Manager assigns a reference number to it. This reference number is
then used by the application to make one (and only one) resource fork the current
fork. Having only one resource fork considered the current fork prevents the
application from loading a wrong resource into memory. Keep in mind that different
files can hold similarly numbered resources. For instance, two files could each hold a
WIND resource with an ID of 128. If a call to GetNewWindow() specified that a WIND
with an ID of 128 be used as the basis for the new window, and the resource forks of
both of these files were open, the program could load the wrong resource. Specifying
which fork to use before calling GetNewWindow() solves this problem.
The Toolbox routine CurResFile() returns the reference number of the resource fork
that's considered current. When an application launches we know that the application's
resource fork is automatically opened and made current. If an application will be
opening other resource forks, it's a good idea to retrieve and save the reference
number of the application resource fork just after application start-up:
short gApplRsrcForkRef;
gApplRsrcForkRef = CurResFile();
When the application opens the resource fork of a different resource file, the
reference number of the newly opened file will be returned to the program. At that
time the program should store this reference number as well. Then, at any time in the
execution of the program either resource fork can be made current by calling the
Toolbox function UseResFile(). The only parameter to this routine is the reference
number of the file to make current. This more involved (but still incomplete) snippet
provides an example:
short gApplRsrcForkRef;
short gFileRsrcForkRef;
gApplRsrcForkRef = CurResFile();
[ Here we'd open the resource fork of a resource file ]
[ and save its reference number in gFileRsrcForkRef ]
UseResFile( gFileRsrcForkRef );
[ Now resource-related tasks, such as calls to ]
[ GetNewWindow(), GetPicture(), and GetResource(), ]
[ use resources from the external resource file ]
UseResFile( gApplRsrcForkRef );
[ Now resource-related tasks use resources from the ]
[ application resource fork ]
Opening and Closing a Resource Fork
The previous snippet left out an important detail - exactly how a program goes about
opening the resource fork of a file. This task is accomplished by calling the Toolbox
routine FSpOpenResFile(). The routine name's leading FSp indicates that the function
is one that works with a file system specification - a variable of type FSSpec. An
FSSpec for a file can be obtained by calling the Toolbox function FSMakeFSSpec(). This
function requires four parameters. Together, the first and second parameters define
where on disk the file named in the third parameter is located. The fourth parameter is
the file system specification, and is filled in by FSMakeFSSpec(). Consider this
snippet:
#define kRsrcFileName "\pMyResourceFile
short volRef = 0;
long dirID = 0;
FSSpec rsrcFSSpec;
FSMakeFSSpec( volRef, dirID, kRsrcFileName, &rsrcFSSpec );
The first parameter is the reference number for the volume that holds the file in
question. Every volume, or drive, connected to the user's machine has a unique volume
reference number. The second parameter is an ID for the directory that holds the file.
Every directory, or folder, on a drive has a unique directory ID. The third parameter
is the file's name. To reference any one file, only these three pieces of information
(volume reference number, directory ID, and file name) are ever needed. From this
information a FSSpec can always be created.
If a file is located in the same folder as the application that's accessing the file, then a
value of 0 can be used in place of both the volume reference number and the directory
ID. For simplicity, that's what we've done in the above example. Plugging in a zero for
the first two parameters is a simple means of getting an FSSpec for a file, but it also
means that the file must be stored in the same folder as the application or the returned
FSSpec will be invalid. In a future Getting Started article we'll explore more fully the
topic of working with files - including how a program accesses files in other
directories and on other disks.
Once the program has the FSSpec of a file, a call to the Toolbox function
FSpOpenResFile() is made to open the resource fork of the file. The first of two